#include "Components/ComponentFurnace.h"
#include "Components/ComponentBlockEntity.h"

#include "Terrain/Terrain.h"
#include "Managers/BlocksManager.h"
#include "Managers/FurnaceRecipesManager.h"

#include "Engine/MathUtils.hpp"
#include "Subsystems/SubsystemExplosions.h"
#include "Subsystems/SubsystemTerrain.h"
#include "Subsystems/SubsystemTimeOfDay.h"
#include "Blocks/LitFurnaceBlock.h"
#include "Network/PackageManager.h"
#include "Network/Packages/ComponentFurnacePackage.h"

#include "GameSingleton.h"
#include "GameRegistry.hpp"
#include "GameThreadPool.hpp"


using namespace PocketSurvival::Engine;

namespace PocketSurvival
{
    uint32_t ComponentFurnace::getSlotCapacity(uint32_t slotIndex, int32_t cellValue)
    {
        if (slotIndex == fuelSlotIndex())
        {
            if (GameSingleton::blocksManager->blocks[Terrain::ExtractContents(cellValue)]->FuelHeatLevel > 0.0f)
            {
                return ComponentInventoryBase::getSlotCapacity(slotIndex, cellValue);
            }
            return 0;
        }
        return ComponentInventoryBase::getSlotCapacity(slotIndex, cellValue);
    }

    void ComponentFurnace::addSlotItems(uint32_t slotIndex, int32_t cellValue, uint32_t count)
    {
        updateSmeltingRecipe = true;
        ComponentInventoryBase::addSlotItems(slotIndex, cellValue, count);
    }
    uint32_t ComponentFurnace::removeSlotItems(uint32_t slotIndex, uint32_t count)
    {
        updateSmeltingRecipe = true;
        return ComponentInventoryBase::removeSlotItems(slotIndex, count);
    }
    void ComponentFurnace::setSlotValue(uint32_t slotIndex, int32_t cellValue, uint32_t cellCount)
    {
        updateSmeltingRecipe = true;
        ComponentInventoryBase::setSlotValue(slotIndex, cellValue, cellCount);
    }

    void ComponentFurnace::update(float dt)
    {
        SubsystemExplosions *subsystemExlosions = GameSingleton::singletonPtr->getSingleton<SubsystemExplosions>();
        SubsystemTerrain *subsystemTerrain = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>();

        const Point3 &coordinates = componentBlockEntity->coordinates;
        if (heatLevel > 0.0f)
        {
            fireTimeRemaining = MathUtils::Max<float>(0.0f, fireTimeRemaining - dt);
            if (fireTimeRemaining == 0.0f)
            {
                heatLevel = 0.0f;
            }
        }
        if (updateSmeltingRecipe)
        {
            updateSmeltingRecipe = false;
            float heatLevel = 0.0f;
            if (heatLevel > 0.0f)
            {
                heatLevel = heatLevel;
            }
            else
            {
                const Slot &slot = slots[fuelSlotIndex()];
                if (slot.count > 0)
                {
                    int32_t num = Terrain::ExtractContents(slot.value);
                    heatLevel = GameSingleton::blocksManager->blocks[num]->FuelHeatLevel;
                }
            }
            FurnaceRecipe *furnRecipe = findSmeltingRecipe(heatLevel);
            if (furnRecipe != smeltingRecipe)
            {
                smeltingRecipe = ((furnRecipe != nullptr && furnRecipe->resultValue != 0) ? furnRecipe : nullptr);
                smeltingProgress = 0.0f;
            }
        }

        if (smeltingRecipe == nullptr)
        {
            heatLevel = 0.0f;
            fireTimeRemaining = 0.0f;
        }
        if (smeltingRecipe != nullptr && fireTimeRemaining <= 0.0f)
        {
            Slot &slot2 = slots[fuelSlotIndex()];
            if (slot2.count > 0)
            {
                int32_t num2 = Terrain::ExtractContents(slot2.value);
                Block *block = GameSingleton::blocksManager->blocks[num2];
                if (block->getExplosionPressure(slot2.value) > 0.0f)
                {
                    slot2.count = 0;
                    subsystemExlosions->tryExplodeBlock(coordinates.posX, coordinates.posY, coordinates.posZ, slot2.value);
                }
                else if (block->FuelHeatLevel > 0.0f)
                {
                    if (smeltingRecipe != nullptr && smeltingRecipe->requiredHeatLevel > block->FuelHeatLevel)
                    {
                        heatLevel = 0.0f;
                        fireTimeRemaining = 0.0f;
                    }
                    else
                    {
                        slot2.count--;
                        fireTimeRemaining = block->FuelFireDuration;
                        heatLevel = block->FuelHeatLevel;
                    }
                }
                onSlotChange(fuelSlotIndex());
            }
        }
        if (fireTimeRemaining <= 0.0f)
        {
            smeltingRecipe = nullptr;
            smeltingProgress = 0.0f;
        }
        if (smeltingRecipe != nullptr)
        {
            smeltingProgress = MathUtils::Min<float>(smeltingProgress + 0.15f * dt / smeltingRecipe->smeltingTimeMultiple, 1.0f);
            isRun = true;
            if (smeltingProgress >= 1.0f)
            {
                for (int32_t index = 0; index < fuelSlotIndex(); index++)
                {
                    Slot &slotItem = slots[index];
                    if (slotItem.count > 0)
                    {
                        slotItem.count--;
                        onSlotChange(index);
                    }
                }
                slots[resultSlotIndex()].value = smeltingRecipe->resultValue;
                slots[resultSlotIndex()].count += smeltingRecipe->resultCount;
                if (smeltingRecipe->remainsValue != 0 && smeltingRecipe->remainsCount > 0)
                {
                    slots[remainsSlotIndex()].value = smeltingRecipe->remainsValue;
                    slots[remainsSlotIndex()].count += smeltingRecipe->remainsCount;
                    onSlotChange(remainsSlotIndex());
                }
                onSlotChange(resultSlotIndex());
                smeltingRecipe = nullptr;
                smeltingProgress = 0.0f;
                updateSmeltingRecipe = true;
            }
        }

        TerrainChunk *chunkAtCell = subsystemTerrain->getTerrainChunk(coordinates.posX >> 4, coordinates.posZ >> 4);
        if (chunkAtCell != nullptr && chunkAtCell->State >= TerrainChunkState::InvalidLight)
        {
            int32_t oldCellValue = subsystemTerrain->getCellValue(coordinates);
            int32_t oldContents = Terrain::ExtractContents(oldCellValue);
            if (heatLevel > 0)
            {
                if (oldContents == FurnaceBlock::Index())
                {
                    subsystemTerrain->changeOldCell(coordinates, oldCellValue, Terrain::ReplaceContents(oldCellValue, LitFurnaceBlock::Index()));
                }
            }
            else
            {
                if (oldContents == LitFurnaceBlock::Index())
                {
                    subsystemTerrain->changeOldCell(coordinates, oldCellValue, Terrain::ReplaceContents(oldCellValue, FurnaceBlock::Index()));
                }
            }
        }
        if (isRun == true)
        {
            if (smeltingRecipe == nullptr && fireTimeRemaining == 0.0f)
            {
                isRun = false;
                GameSingleton::packageManager->addSendPackageToQueue(std::make_shared<ComponentFurnacePackage>(this));
            }
        }
    }

    FurnaceRecipe *ComponentFurnace::findSmeltingRecipe(float heatLevel)
    {
        if (heatLevel > 0.0f)
        {
            memset(matchedIngredients, 0, sizeof(matchedIngredients));
            uint32_t num = UINT32_MAX;
            for (int32_t i = 0; i < furnaceColumnsSize; i++)
            {
                for (int32_t j = 0; j < furnaceRowSize; j++)
                {
                    int32_t num2 = i + j * 3;
                    int32_t slotIndex = i + j * furnaceColumnsSize;
                    int32_t slotValue = getSlotValue(slotIndex);
                    uint32_t slotCount = getSlotCount(slotIndex);
                    if (slotCount > 0)
                    {
                        matchedIngredients[num2] = slotValue;
                        num = MathUtils::Min<uint32_t>(num, slotCount);
                    }
                }
            }
            FurnaceRecipe *furnaceRecipe = GameSingleton::furnaceRecipesManager->findFurnaceRecipes(matchedIngredients);
            if (furnaceRecipe != nullptr)
            {
                if (furnaceRecipe->requiredHeatLevel > heatLevel)
                {
                    return nullptr;
                }
                const Slot &slot = slots[resultSlotIndex()];
                int32_t num3 = Terrain::ExtractContents(furnaceRecipe->resultValue);
                if (slot.count != 0 && (furnaceRecipe->resultValue != slot.value || furnaceRecipe->resultCount + slot.count > GameSingleton::blocksManager->blocks[num3]->MaxStacking))
                {
                    return nullptr;
                }
                if (furnaceRecipe->remainsValue != 0 && furnaceRecipe->remainsCount > 0)
                {
                    if (slots[remainsSlotIndex()].count == 0 || slots[remainsSlotIndex()].value == furnaceRecipe->remainsValue)
                    {
                        if (GameSingleton::blocksManager->blocks[Terrain::ExtractContents(furnaceRecipe->remainsValue)]->MaxStacking - slots[remainsSlotIndex()].count < furnaceRecipe->remainsCount)
                        {
                            furnaceRecipe = nullptr;
                        }
                    }
                    else
                    {
                        furnaceRecipe = nullptr;
                    }
                }
            }
            return furnaceRecipe;
        }
        return nullptr;
    }

    void ComponentFurnace::save(nlohmann::json &jsonData)
    {
        ComponentInventoryBase::save(jsonData);
        jsonData["FireTimeRemaining"] = fireTimeRemaining;
        jsonData["HeatLevel"] = heatLevel;
    }
    void ComponentFurnace::load(const nlohmann::json &jsonData)
    {
        ComponentInventoryBase::load(jsonData);
        furnaceRowSize = 1;
        furnaceColumnsSize = 2;
        fireTimeRemaining = jsonData["FireTimeRemaining"].get<float>();
        heatLevel = jsonData["HeatLevel"].get<float>();
        updateSmeltingRecipe = true;

        componentBlockEntity = GameSingleton::gameRegistry->get<ComponentBlockEntity *>(entity);
    }

    static const std::string componentName = "Furnace";
    const std::string &ComponentFurnace::getName() const
    {
        return componentName;
    }
    const std::string &ComponentFurnace::GetName()
    {
        return componentName;
    }
} // namespace PocketSurvival
